package db_utils

import (
	"database/sql"
	"fmt"
	"github.com/jmoiron/sqlx"
)

type ConvertError struct {
	StatusCode int
	Message    string
	Details    map[string]interface{}
}

// 自定义错误类型，需要实现 func Error() string 方法
func (ce *ConvertError) Error() string {
	return fmt.Sprintf("Convert error (status %d): %s", ce.StatusCode, ce.Message)
}

type Option func(*ConvertError)

func ConvertErrorWithDetail(key string, v interface{}) Option {
	return func(ce *ConvertError) {
		ce.Details[key] = v
	}
}

func ConvertErrorWithMessage(Message string) Option {
	return func(ce *ConvertError) {
		ce.Message = Message
	}
}

func ConvertErrorWithStatusCode(StatusCode int, ce *ConvertError) Option {
	return func(ce *ConvertError) {
		ce.StatusCode = StatusCode
	}
}

func NewConvertError(StatusCode int, Message string, opts ...Option) *ConvertError {
	ce := &ConvertError{
		StatusCode: StatusCode,
		Message:    Message,
		Details:    map[string]interface{}{},
	}
	for _, opt := range opts {
		opt(ce)
	}
	return ce
}

type ConvertValue interface {
	ConvertValue(val interface{}, colType string) interface{}
}

// ValueHandleInterface  处理数值类型转换
type ValueHandleInterface interface {
	addConvertValue(string, ConvertValue) error
	getConvertValue(string) (ConvertValue, error)
}

type DBConvert struct {
	ConvertValueMap map[string]ConvertValue
}

func (dc DBConvert) addConvertValue(dbName string, value ConvertValue) error {
	_, ok := dc.ConvertValueMap[dbName]
	if ok {
		return NewConvertError(500, fmt.Sprintf("dbName %s is exist ", dbName), ConvertErrorWithDetail(dbName, fmt.Sprintf("dbName %s is exist ", dbName)))
	}
	dc.ConvertValueMap[dbName] = value
	return nil
}

func (dc DBConvert) getConvertValue(dbName string) (ConvertValue, error) {
	value, ok := dc.ConvertValueMap[dbName]
	if !ok {
		return nil, NewConvertError(501, fmt.Sprintf("dbName %s is not exist ", dbName), ConvertErrorWithDetail(dbName, fmt.Sprintf("dbName %s is not exist ", dbName)))
	}
	return value, nil
}

type ColumnInfo struct {
	TableName     string         `db:"table_name" json:"tableName"`
	ColumnName    string         `db:"column_name" json:"columnName"`
	DataType      string         `db:"data_type" json:"dataType"`
	IsNullable    string         `db:"is_nullable" json:"isNullable"`
	ColumnDefault sql.NullString `db:"column_default" json:"columnDefault"`
	MaxLength     sql.NullInt64  `db:"character_maximum_length" json:"maxLength"`
	Precision     sql.NullInt64  `db:"numeric_precision" json:"precision"`
	Scale         sql.NullInt64  `db:"numeric_scale" json:"scale"`
	ColumnKey     sql.NullString `db:"column_key" json:"columnKey"`
	Extra         sql.NullString `db:"extra" json:"extra"`
	Comment       sql.NullString `db:"column_comment" json:"comment"`
}

type PrimaryKeyInfo struct {
	TableName  string `db:"table_name" json:"tableName"`
	ColumnName string `db:"column_name" json:"columnName"`
	KeySeq     int    `db:"key_seq" json:"keySeq"`
}

type ForeignKeyInfo struct {
	ConstraintName    string `db:"constraint_name" json:"constraintName"`
	ColumnName        string `db:"column_name" json:"columnName"`
	TableName         string `db:"table_name" json:"tableName"`
	ForeignTableName  string `db:"foreign_table_name" json:"foreignTableName"`
	ForeignColumnName string `db:"foreign_column_name" json:"foreignColumnName"`
	UpdateRule        string `db:"update_rule" json:"updateRule"`
	DeleteRule        string `db:"delete_rule" json:"deleteRule"`
}

type IndexInfo struct {
	TableName  string `db:"table_name" json:"tableName"`
	IndexName  string `db:"index_name" json:"indexName"`
	ColumnName string `db:"column_name" json:"columnName"`
	NonUnique  int    `db:"non_unique" json:"nonUnique"`
	IndexType  string `db:"index_type" json:"indexType"`
	IndexDef   string `db:"index_def" json:"indexDef"`
}

type Databases interface {
	GetDatabases() ([]string, error)
}

type Tables interface {
	GetTables(schema string) ([]string, error)
}
type TableColumns interface {
	GetTableColumns(schema, tableName string) ([]ColumnInfo, error)
	GetAllTableColumns(schema string) ([]ColumnInfo, error)
}

type PrimaryKeys interface {
	GetPrimaryKeys(schema, tableName string) ([]PrimaryKeyInfo, error)
	GetAllPrimaryKeys(schema string) ([]PrimaryKeyInfo, error)
}

type ForeignKeys interface {
	GetForeignKeys(schema, tableName string) ([]ForeignKeyInfo, error)
	GetAllForeignKeys(schema string) ([]ForeignKeyInfo, error)
}

type Indexes interface {
	GetIndexes(schema, tableName string) ([]IndexInfo, error)
	GetAllIndexes(schema string) ([]IndexInfo, error)
}

// Queryer is an interface used by Get and Select
type DB interface {
	Query(query string, args ...interface{}) (*sql.Rows, error)
	Queryx(query string, args ...interface{}) (*sqlx.Rows, error)
	QueryRowx(query string, args ...interface{}) *sqlx.Row
	Exec(query string, args ...interface{}) (sql.Result, error)
	Select(dest interface{}, query string, args ...interface{}) error
	Get(dest interface{}, query string, args ...interface{}) error
}

type DataSchema interface {
	Indexes
	PrimaryKeys
	ForeignKeys
	TableColumns
	Tables
	Databases
	GetSchema() string
	GetDB() DB
}

type DataConvert interface {
	DataSchema
	// 转换数据核心方法
	ConvertData(destDataSchema DataSchema, config ConvertConfig) error
}

type DBType string

const (
	DBType_Mysql = "mysql"
	DBType_Pgsql = "pgsql"
)

type ConvertConfig struct {
	// 源数据信息
	Source DBConfig
	// 目的数据信息
	Dest DBConfig
	// map[source_table]map[dest_table]map[source_column]dest_column
	/**
	student 			------------> source_table
	 stu    			------------> dest_able
	  age age1			------------> source_column dest_column
	  sex sex			------------> source_column dest_column
	  name name1		------------> source_column dest_column
	*/
	Result map[string]map[string]map[string]string
}

type DBConfig struct {
	DBType     string
	Schema     string
	Tables     []string
	ColumnsMap map[string][]string
}
